package com.treeman.user.service;

import java.util.Date;
import java.util.List;
import java.util.Set;

import org.apache.log4j.Logger;
import org.hibernate.HibernateException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.security.authentication.dao.SaltSource;
import org.springframework.security.authentication.encoding.PasswordEncoder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.treeman.api.ApiRequestError.ApiError;
import com.treeman.api.json.user.UserJson;
import com.treeman.api.json.user.UserLocationJson;
import com.treeman.api.security.exception.InvalidUsernameOrPasswordException;
import com.treeman.friend.Invitation;
import com.treeman.friend.dao.FriendInvitationDao;
import com.treeman.friend.exception.InvitationAlreadyExistsException;
import com.treeman.friend.exception.InvitationNotFoundException;
import com.treeman.user.User;
import com.treeman.user.dao.UserDao;
import com.treeman.user.exception.UserExistsException;
import com.treeman.user.exception.UserNotFoundException;
import com.treeman.user.localization.LocalizationManager;
import com.treeman.user.localization.UserLocalization;

@Service(value = "userService")
public class UserServiceImpl implements UserService {

	private static Logger log = Logger.getLogger(UserServiceImpl.class);

	@Autowired
	private UserDao userDao;

	@Autowired
	private FriendInvitationDao invitationDao;

	@Autowired
	private SaltSource saltSource;

	@Autowired
	private PasswordEncoder passwordEncoder;

	@Override
	@Transactional
	public void registerUser(User user) throws UserExistsException {

		log.debug("User registration: " + user.getUsername());

		if (userDao.getUserByUsername(user.getUsername()) != null)
			throw new UserExistsException();

		userDao.addUser(user);
		user.setPassword(passwordEncoder.encodePassword(user.getPassword(),
				saltSource.getSalt(user)));
	}

	@Override
	@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
	public User getUserByUsername(String username) throws UserNotFoundException {
		User user = userDao.getUserByUsername(username);
		if (user != null)
			return user;
		else
			throw new UserNotFoundException();
	}

	@Override
	@Transactional
	public User authenticateUser(String username, String password)
			throws InvalidUsernameOrPasswordException {

		log.debug("User authentication. User: " + username);

		User user = userDao.getUserByUsername(username);
		if (user == null)
			throw new InvalidUsernameOrPasswordException(
					ApiError.INVALID_USERNAME_OR_PASSWORD,
					"Invalid username or password!");

		String encodedPassword = passwordEncoder.encodePassword(password,
				saltSource.getSalt(user));
		if (user != null && user.getPassword().equals(encodedPassword)) {
			log.debug("Authentication: success");
			user.setLastLoginDate(new Date());
			return user;
		} else {
			log.debug("Authentication: failure");
			throw new InvalidUsernameOrPasswordException(
					ApiError.INVALID_USERNAME_OR_PASSWORD,
					"Invalid username or password!");
		}
	}

	@Override
	@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
	public UserJson getUserJsonByUsername(String username)
			throws UserNotFoundException {

		User user = userDao.getUserByUsername(username);
		if (user == null)
			throw new UserNotFoundException();

		return new UserJson(user);
	}

	@Override
	@Transactional
	public UserDetails loadUserByUsername(String arg0)
			throws UsernameNotFoundException, DataAccessException {
		UserDetails userDetails = userDao.getUserByUsername(arg0);
		if (userDetails == null)
			throw new UsernameNotFoundException(arg0);
		return userDetails;
	}

	@Transactional
	@Override
	public void login(User user) {
		user.setLastLoginDate(new Date());
	}

	@Transactional
	@Override
	public List<User> findUsers(String firstname, String lastName,
			String username) {

		return userDao.findUsers(firstname, lastName, username);
	}

	@Transactional
	@Override
	public Set<User> getUserFriends(String username) {

		if (username == null) {
			log.debug("Username is null!");
			return null;
		}

		return userDao.getUserFriends(username);
	}

	@Transactional
	@Override
	public void inviteFriend(String username, String friendUsername)
			throws UserNotFoundException, InvitationAlreadyExistsException {

		log.debug("Dodawanie nowego zaproszenia: " + username + " zaprasza "
				+ friendUsername);

		User user = userDao.getUserByUsername(username);
		User friend = userDao.getUserByUsername(friendUsername);

		if (friend == null) {
			log.debug("Użytkownik zapraszany nei istnieje: " + friendUsername);
			throw new UserNotFoundException(friendUsername);
		}

		if (userDao.isAlreadyInvited(user, friend)) {
			log.debug("Juz istnieje zaproszenie do znajomych pomiędzy tymi uzytkownikami!");
			throw new InvitationAlreadyExistsException();
		}

		// Zapisujemy zaproszenie w bazie
		invitationDao.saveInvitation(user, friend);
	}

	@Transactional
	@Override
	public Set<Invitation> getRecievedInvitations(String username) {

		if (username == null || username.equals("")) {
			log.debug("Brak nazwy uzytkownika!");
			return null;
		}
		return userDao.getReceivedInvitations(username);
	}

	@Transactional
	@Override
	public void acceptInvitationFromUser(String acceptingUsername,
			String requestingUsername) throws InvitationNotFoundException {

		User acceptingUser = userDao.getUserByUsername(acceptingUsername);
		User requestingUser = userDao.getUserByUsername(requestingUsername);

		if (requestingUser == null)
			throw new InvitationNotFoundException(requestingUsername);

		try {
			// Usuwamy zaproszenie
			userDao.removeInvitation(requestingUser, acceptingUser);
		} catch (HibernateException e) {
			log.debug("Nie znaleziono podanego zaproszenia...");
			throw new InvitationNotFoundException(requestingUsername);
		}

		// Dodajemy do znajomych
		acceptingUser.getFriends().add(requestingUser);
		requestingUser.getFriends().add(acceptingUser);
	}

	@Transactional
	@Override
	public void rejectInvitationFromUser(String rejectingUsername,
			String requestingUsername) throws InvitationNotFoundException {

		User rejectingUser = userDao.getUserByUsername(rejectingUsername);
		User requestingUser = userDao.getUserByUsername(requestingUsername);

		if (requestingUser == null)
			throw new InvitationNotFoundException(requestingUsername);

		try {
			// Usuwamy zaproszenie
			userDao.removeInvitation(requestingUser, rejectingUser);
		} catch (HibernateException e) {
			log.debug("Nie znaleziono podanego zaproszenia...");
			throw new InvitationNotFoundException(requestingUsername);
		}
	}

	@Transactional
	@Override
	public UserLocationJson getUserLocationJson(String username,
			String requesterUsername) throws UserNotFoundException {
		User user = userDao.getUserByUsername(username);
		User requester = userDao.getUserByUsername(requesterUsername);

		if (user == null)
			throw new UserNotFoundException();

		if (!user.getFriends().contains(requester))
			return null;

		UserLocalization loc = LocalizationManager.getInstance()
				.getUserLocalization(username);
		if (loc != null) {
			UserLocationJson location = new UserLocationJson(
					new UserJson(user), loc.getLongitude(), loc.getLatitude());

			return location;
		} else
			return null;

	}

	@Transactional
	@Override
	public void changeUserPassword(String username, String newPassword,
			String oldPassword) throws InvalidUsernameOrPasswordException {
		User user = userDao.getUserByUsername(username);

		String encodedPassword = passwordEncoder.encodePassword(oldPassword,
				saltSource.getSalt(user));
		if (user != null && user.getPassword().equals(encodedPassword)) {
			log.debug("Hasło poprawne. Zmieniam hasło na " + newPassword);

			user.setPassword(passwordEncoder.encodePassword(newPassword,
					saltSource.getSalt(user)));
		} else
			throw new InvalidUsernameOrPasswordException(
					ApiError.INVALID_USERNAME_OR_PASSWORD, "Wrong password");
	}
}
